// This file is part of OpenMeca, an easy software to do mechanical simulation.
//
// Author(s)    :  - Damien ANDRE  <openmeca@gmail.com>
//
// Copyright (C) 2012 Damien ANDRE
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.


#ifndef OpenMeca_Core_SetOf_hpp
#define OpenMeca_Core_SetOf_hpp



#include <cassert>
#include <list>
#include <iostream>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>

#include "OpenMeca/Core/SetOfBase.hpp"

namespace OpenMeca
{
  namespace Core
  {

    // ** Declaration of SetOf class ** 
    template<typename type>
    class SetOf : public SetOfBase<type>
    {
    public:
      static SetOf<type>& GetGlobalSet();
      typedef typename std::list<type *>::iterator it;
      typedef typename std::list<type *>::const_iterator const_it;

    public:

      SetOf();
      SetOf(const SetOf &);
      SetOf& operator=(const SetOf &);
      type& operator()(unsigned int);

      ~SetOf();

      void AddItem(type&);      
      void RemoveItem(type&);
      void ClearAndDelete();
      void DeleteAllItem();
      //unsigned int GetRank (type *) const;

      unsigned int GetRank (const type *) const;
      void EraseDoublet();
      
      void CheckForDoublet();
      void UnCheckForDoublet();

      bool IsGlobalSet() const;

    private:
      friend class boost::serialization::access;
      template<class Archive> void save(Archive & ar, const unsigned int) const;
      template<class Archive> void load(Archive & ar, const unsigned int);
      BOOST_SERIALIZATION_SPLIT_MEMBER() 

    private:
      static SetOf<type>* globalSet_;
      
    private:
      bool checkForDoublet_;
    };

    
    // ** Declaration of AutoRegister class **
    template <class T>
    class AutoRegister
    {
    
    public:
      static bool Exist(T&);
      static SetOf<T>& GetGlobalSet();
      static void Debug();

    public :
      void AddSet(SetOf<T>&);
      void RemoveSet(SetOf<T>&);

    protected: 
      AutoRegister();
      virtual ~AutoRegister();
      

    private :
      AutoRegister(const AutoRegister&);             //Not Allowed
      AutoRegister& operator=(const AutoRegister&);  //Not Allowed
    
    private:
      std::list<SetOf<T>*> allSet_;
    }; 


    // ** Definition of SetOf class ** 
    template<typename type> 
    SetOf<type>* SetOf<type>::globalSet_=0;

    template<typename type> 
    inline SetOf<type>&
    SetOf<type>::GetGlobalSet()
    {
      if (globalSet_==0)
	globalSet_ = new SetOf<type>();
      return *globalSet_;
    }


    template<typename type> 
    template<class Archive>
    inline void
    SetOf<type>::save(Archive & ar, const unsigned int) const
    {
      std::cout << "SetOf<type>::save" << std::endl;
      unsigned int totItemNumber = SetOfBase<type>::set_.size();
      ar << BOOST_SERIALIZATION_NVP(totItemNumber);
      typename std::list<type *>::const_iterator it;
      for (it=SetOfBase<type>::set_.begin() ; it != SetOfBase<type>::set_.end(); it++ )
	{
	  ar << BOOST_SERIALIZATION_NVP(*it);
	} 
    }

    template<typename type> 
    template<class Archive>
    inline void
    SetOf<type>::load(Archive & ar, const unsigned int)
     {
       bool isGlobal = IsGlobalSet();
       unsigned int totItemNumber = 0;
       ar >> BOOST_SERIALIZATION_NVP(totItemNumber);
       for (unsigned int i=0;  i< totItemNumber; ++i)
	 {
	   type* ptr=0;
	   ar >> BOOST_SERIALIZATION_NVP(ptr);
	   assert(ptr != 0);

	   if (!isGlobal)
	     AddItem(*ptr);
	 }
     }
      
    template<typename type> 
    inline
    SetOf<type>::~SetOf()
    {
      typename std::list<type *>::iterator it;
      for ( it=SetOfBase<type>::set_.begin() ; it != SetOfBase<type>::set_.end(); it++ )
	{
	  type& item = **it;
	  static_cast<AutoRegister<type>& >(item).RemoveSet(*this);
	}
    }

    
    template<typename type> 
    inline
    SetOf<type>::SetOf(const SetOf &set)
      : SetOfBase<type>(set),
	checkForDoublet_(set.checkForDoublet_)
    {
      typename std::list<type *>::iterator it;
      for ( it=SetOfBase<type>::set_.begin() ; it != SetOfBase<type>::set_.end(); it++ )
	{
	  type& item = **it;
	  static_cast<AutoRegister<type>& >(item).AddSet(*this);
	}
    }
    
    template<typename type> 
    inline SetOf<type>& 
    SetOf<type>::operator=(const SetOf &set)
    {
      SetOfBase<type>::set_ = set.SetOfBase<type>::set_;
      checkForDoublet_ = SetOfBase<type>::set_.checkForDoublet_;
      typename std::list<type *>::iterator it;
      for ( it=SetOfBase<type>::set_.begin() ; it != SetOfBase<type>::set_.end(); it++ )
	{
	  type& item = **it;
	  static_cast<AutoRegister<type>& >(item).AddSet(*this);
	}
      return *this;
    }


    template<typename type> 
    inline 
    SetOf<type>::SetOf()
      :SetOfBase<type>(),
       checkForDoublet_(true)
					  
    {
    }

        
    template<typename type>
    inline void
    SetOf<type>::AddItem(type& item)
    {
      typename std::list<type *>::iterator it;
      for (it=SetOfBase<type>::set_.begin() ; it != SetOfBase<type>::set_.end(); it++ )
	{
	  if (*it==&item && checkForDoublet_==true)
	    {
	      assert(0);
	    }
	}
     SetOfBase<type>::set_.push_back(&item);
     static_cast<AutoRegister<type>& >(item).AddSet(*this);
    }
    
    template<typename type>
    inline void
    SetOf<type>::EraseDoublet()
    {
      for (unsigned int i=0; i<SetOfBase<type>::GetTotItemNumber(); ++i)
	{
	  for (unsigned int j=i+1; j<SetOfBase<type>::GetTotItemNumber(); ++j)
	    {
	      if (SetOfBase<type>::set_[i]==SetOfBase<type>::set_[j])
		{
		  RemoveItem(*SetOfBase<type>::set_[j]);
		}
	    }
	}
    }

    template<typename type>
    inline void
    SetOf<type>::RemoveItem(type& item)
    {
      typename std::list<type *>::iterator it;
      for ( it=SetOfBase<type>::set_.begin() ; it != SetOfBase<type>::set_.end(); it++ )
	{
	  if (*it==&item)
	    {
	      SetOfBase<type>::set_.erase(it);
	      static_cast<AutoRegister<type>& >(item).RemoveSet(*this);
	      return;
	    }
	}
      assert(false);
    }

   

    template<typename type>
    inline void
    SetOf<type>::ClearAndDelete()
    {
      DeleteAllItem();
      SetOfBase<type>::set_.clear();
    }

    template<typename type>
    inline void
     SetOf<type>::DeleteAllItem()
    {
      typename std::list<type *>::iterator it;
      while(SetOfBase<type>::GetTotItemNumber() > 0)
       {
	 const unsigned int size = SetOfBase<type>::GetTotItemNumber();
	 it=SetOfBase<type>::set_.begin();
	 delete (*it);
	 assert(SetOfBase<type>::GetTotItemNumber() < size);
       }
    }



    template<typename type>
    inline void 
    SetOf<type>::CheckForDoublet()
    {
      checkForDoublet_ = true;
    }

    template<typename type>
    inline void 
    SetOf<type>::UnCheckForDoublet()
    {
      checkForDoublet_ = false;
    }


    template<typename type>
    inline  bool
    SetOf<type>::IsGlobalSet() const
    {
      return (this == &SetOf<type>::GetGlobalSet());
    }

    // ** Definition of AutoRegister class ** 
    template<class T> 
    inline SetOf<T>&
    AutoRegister<T>::GetGlobalSet()
    {
      return SetOf<T>::GetGlobalSet();
    }
    

    template<class T>
    inline bool
    AutoRegister<T>::Exist(T& item)
    {
      return GetGlobalSet().Contain(item);
    }

    template<class T>
    inline void
    AutoRegister<T>::Debug()
    {
      std::cout << "---- AutoRegister<T>::Debug() ----" << std::endl;
      std::cout << " container adress :" << &GetGlobalSet() << std::endl;
      typename Core::SetOf<T>::it it;
      for (it = GetGlobalSet().Begin() ; it != GetGlobalSet().End(); it++ )
	std::cout << "Contain, " << (*it)->GetName() << std::endl;
      std::cout << "---- ------------------------ ----" << std::endl;
    }

    template<class T>
    inline
    AutoRegister<T>::AutoRegister()
    {
      GetGlobalSet().AddItem(*static_cast<T*>(this));
    }


    template<class T>
    inline
    AutoRegister<T>::~AutoRegister()
    {
      typename std::list<SetOf<T>*>::iterator it;
      while (allSet_.size() > 0)
	{
	  unsigned int size = allSet_.size();
	  (*allSet_.begin())->RemoveItem(*static_cast<T*>(this));
	  assert(allSet_.size() == size-1);
	}
      assert(GetGlobalSet().Contain(static_cast<T*>(this))==false);
    }

    template<class T>
    inline void 
    AutoRegister<T>::AddSet(SetOf<T>& set)
    {
      allSet_.push_back(&set);
    }
    
    template<class T>
    inline void 
    AutoRegister<T>::RemoveSet(SetOf<T>& set)
    {
      typename std::list<SetOf<T>*>::iterator it;
      for ( it=allSet_.begin() ; it != allSet_.end(); it++ )
	{
	  if ((*it)==&set)
	    {
	      allSet_.erase(it); 
	      return;
	    }
	}
      assert(0);
    }
    

  }
}

#endif
